/**
 * ym2常用封装
 * @author cleverstone
 * @dependency jQuery.js
 * @dependency lodash.js
 * @since ym2.0
 */
!function (global, jQuery, lodash, factory) {
    "use strict";
    if (typeof module !== "undefined" && module.exports) {
        module.exports = factory(global, jQuery, lodash);
    } else if (typeof define === "function" && define.amd) {
        define('ym2', [], factory(global, jQuery, lodash));
    } else {
        global.ym2 = factory(global, jQuery, lodash);
    }
}(typeof window === void 0 ? this : window, (function (jQuery) {
    return typeof jQuery === void 0 ? require('jquery') : jQuery;
}(window ? window.jQuery : void 0)), (function (lodash) {
    return typeof lodash === void 0 ? require('lodash') : lodash;
}(window ? window._ : void 0)), function (global, jQuery, lodash) {

    var Ym2Constructor = function () {
    };

    var noop = function () {};

    /**
     * 获取应用元数据
     * @returns {number}
     */
    Ym2Constructor.prototype.getHttpSuccessCode = function () {
        return Number(jQuery('meta[name=http-success-code]').attr('content'));
    };
    Ym2Constructor.prototype.getHttpFailureCode = function () {
        return Number(jQuery('meta[name=http-failure-code]').attr('content'));
    };
    Ym2Constructor.prototype.getHttpUnauthorizedCode = function () {
        return Number(jQuery('meta[name=http-unauthorized-code]').attr('content'));
    };
    Ym2Constructor.prototype.getHttpAjaxIdentity = function () {
        return jQuery('meta[name=http-ajax-identity]').attr('content');
    };
    Ym2Constructor.prototype.getAdminRouteLogin = function () {
        return jQuery('meta[name=admin-route-login]').attr('content');
    };
    Ym2Constructor.prototype.getAppThemeColor = function () {
        return jQuery('meta[name=app-theme-color]').attr('content');
    };

    /**
     * 字节转短语句
     * @param bytes
     * @returns {string}
     */
    Ym2Constructor.prototype.asShortSize = function (bytes) {
        if (!bytes) return "0B";
        var sizeFormatBase = 1024;
        var options = ["B", "K", "M", "G", "TB", "PB", "EB", "ZB", "YB", "BB"];
        var i = Math.floor(Math.log(bytes) / Math.log(sizeFormatBase));

        return (bytes / Math.pow(sizeFormatBase, i)).toFixed(2) + "" + options[i];
    };

    /**
     * 秒数转短语句
     * @param msecends
     * @returns {string}
     */
    Ym2Constructor.prototype.asShortTime = function (msecends) {
        var ms = (msecends % 1000) + "";
        var s = (Math.floor(msecends / 1000) % 60) + "";
        var i = (Math.floor(Math.floor(msecends / 1000) / 60) % 60) + "";
        var h = Math.floor(Math.floor(Math.floor(msecends / 1000) / 60) / 60) + "";

        if (ms.length <= 0) ms = "000";
        if (ms.length === 1) ms = "00" + ms;
        if (ms.length === 2) ms = "0" + ms;

        if (s.length <= 0) s = "00";
        if (s.length === 1) s = "0" + s;

        if (i.length <= 0) i = "00";
        if (i.length === 1) i = "0" + i;

        if (h.length <= 0) h = "00";
        if (h.length === 1) h = "0" + h;

        return h + ":" + i + ":" + s + " " + ms;
    };

    /**
     * 数字滚动
     * ```js
     *   ym2.countScroll("number1", {
     *      time: 8000,         // 最大持续时间
     *      num: 4540.34,       // 数字
     *      regulator: 100,     // 速度调节
     *      bit: 2,             // 小数点保留
     *  });
     * ```
     * @param targetEle
     * @param options
     */
    Ym2Constructor.prototype.countScroll = function (targetEle, options) {
        options = options || {};

        var $this = document.getElementById(targetEle),
            time = options.time,
            finalNum = options.num,
            regulator = options.regulator,
            step = finalNum / (time / regulator),
            count = 0.00,
            initial = 0;

        var timer = setInterval(function () {
            count = count + step;
            if (count >= finalNum) {
                clearInterval(timer);
                count = finalNum;
            }
            var t = count.toFixed(options.bit ? options.bit : 0);

            if (t == initial) return;
            initial = t;
            $this.innerHTML = initial;
        }, 30);
    };

    /**
     * 是否移动端
     * @returns {boolean}
     */
    Ym2Constructor.prototype.isMobile = function () {
        if ( global.layui && layui.device ) {
            var device = layui.device();
            return device.mobile;
        } else {
            var userAgent = global.navigator.userAgent.toLowerCase();
            return /Android|webOS|iPhone|iPad|iPod|BlackBerry/gi.test(userAgent);
        }
    };

    /**
     * 填充参数(一次)
     * @param param 参数
     * @param data 数据
     */
    Ym2Constructor.prototype.fillParams = function (param, data) {
        var result = {};
        var field;
        var content;
        var dataKeys = this.keys(data);

        for (var i in param) {
            if (jQuery.isNumeric(i)) { // ["id"]
                field = param[i];
                if (lodash.indexOf(dataKeys, field) > -1) {
                    content = data[field];
                } else {
                    throw `字段${field}不在数据data中`;
                }
            } else {
                var value = param[i] + '';
                var firstStr = value.substr(0, 1);
                if (firstStr === ':') { // {"id": ":order_id"}
                    field = i;
                    var actualField = value.substr(1);
                    if (lodash.indexOf(dataKeys, actualField) > -1) {
                        content = data[actualField];
                    } else {
                        throw `字段${value}不在数据data中`;
                    }
                } else { // {"id": 1}
                    field = i;
                    content = param[i];
                }
            }

            result[field] = content;
        }

        return result;
    };

    /**
     * 填充参数(多次)
     * @param param 参数
     * @param data 数据
     */
    Ym2Constructor.prototype.fillParamsMany = function (param, data) {
        var that = this;

        var result = {};
        var field;
        var dataKeys;
        var isNumeric;

        if (that.keys(param).length) {
            for (var i in param) {
                isNumeric = jQuery.isNumeric(i);
                data.forEach(function (value, index, array) {
                    dataKeys = that.keys(value);
                    if (isNumeric) { // ["id"]
                        field = param[i];
                        if (lodash.indexOf(dataKeys, field) > -1) {
                            if (result[field]) {
                                result[field].push(value[field]);
                            } else {
                                result[field] = [];
                                result[field].push(value[field]);
                            }

                        } else {
                            throw `字段${field}不在数据data的item中`;
                        }
                    } else {
                        var item = param[i] + '';
                        var firstStr = item.substr(0, 1);
                        if (firstStr === ':') { // {"id": ":order_id"}
                            field = i;
                            var actualField = item.substr(1);
                            if (lodash.indexOf(dataKeys, actualField) > -1) {
                                if (result[field]) {
                                    result[field].push(value[actualField]);
                                } else {
                                    result[field] = [];
                                    result[field].push(value[actualField]);
                                }

                            } else {
                                throw `字段${item}不在数据data的item中`;
                            }
                        } else { // {"id": 1}
                            if (result[i] === undefined) {
                                // 只写一次
                                result[i] = param[i];
                            }
                        }
                    }
                });
            }
        }

        return result;
    };

    /**
     * layer窗口兼容移动端参数处理
     * @param width layer窗口宽度
     * @returns {{width: string, closeBtn: number}}
     */
    Ym2Constructor.prototype.layerScreenOptions = function (width) {
        var isMobile = this.isMobile();
        // 1: 内部按钮  2: 外部按钮
        var closeBtn = (isMobile || width === '100%') ? 1 : 2;
        width = isMobile ? "100%" : width;

        return {width: width, closeBtn: closeBtn};
    };

    /**
     * 获取基于当前layer窗口的来源窗口名。
     * @param context 上下文
     *  - undefined
     *  - "self"
     *  - "parent"
     *  - "parent.parent.***"
     */
    Ym2Constructor.prototype.layerRefererWindowName = function ( context ) {
        context = context || "self";
        if ( context === "self" ) {
            return global.name;
        } else if (  context.substring(0, 6) === "parent" ) {
            var keysMap = context.split('.');
            var currentWin = global;
            var refererWinName = currentWin.__REFERER_WIN_NAME;

            var parentWin;
            for (var i = 0; i < keysMap.length; i++) {
                parentWin = refererWinName ? top[ refererWinName ] : currentWin.parent;
                currentWin = parentWin;
                refererWinName = parentWin.__REFERER_WIN_NAME;
            }

            return parentWin.name;
        } else {
            console.error("The param [ context ]：" + context + " undefined in ym2.layerRefererWindowName(). ");
            return "";
        }
    };

    /**
     * angular中的checkbox数据集处理
     * @param data
     * @returns {Array}
     */
    Ym2Constructor.prototype.filterCheckboxValue = function (data) {
        data = data || {};
        var result = [];
        for (var i in data) {
            if (Number(data[i]) === 1) {
                result.push(i);
            }
        }

        return result;
    };

    /**
     * 判断值是否为空
     * @param value
     * @returns {boolean}
     */
    Ym2Constructor.prototype.isEmpty = function (value) {
        // undefined
        if (value === undefined) return true;
        // null
        if (value === null) return true;
        // 空字符
        if (value === '') return true;

        var valueType = this.typeOf(value);
        if (valueType === 'object' || valueType === 'array') {
            // [] || {}
            if (!this.keys(value).length) {
                return true;
            }

            for (var i in value) {
                if (this.isEmpty(value[i])) {
                    return true;
                }
            }
        }

        return false;
    };

    /**
     * 字符串转正则表达式
     * @param str
     * @returns {RegExp}
     */
    Ym2Constructor.prototype.strToRegexp = function (str) {
        //检查字符串是否是以/开头和结尾
        if (!str.startsWith('/')) {
            throw new Error("Invalid regex string format. Expected /pattern/flags.");
        }

        var lastIndex = str.lastIndexOf('/');
        if (lastIndex === 0) {
            throw new Error("Invalid regex string format. Expected /pattern/flags.");
        }

        var pattern = str.slice(1, lastIndex);
        var flags = str.slice(lastIndex + 1);

        return new RegExp(pattern, flags);
    }

    /**
     * sweet alert常用封装
     * @type {{}}
     */
    Ym2Constructor.prototype.swal = {
        // 询问
        question: function (swal, title, description, successCallback, cancelCallback) {
            swal.fire({
                title: title || '',
                text: description || '',
                icon: 'question',
                showCancelButton: true,
                backdrop: "rgba(0,0,0,.3)",
                allowOutsideClick: true,
                allowEscapeKey: true,
                keydownListenerCapture: true, //防止bootstrap-modal esc键冲突。
                confirmButtonText: '确定',
                cancelButtonText: '取消',
            }).then(successCallback).then(cancelCallback || noop);
        },
        // 成功
        ok: function (swal, description, confirmCallback) {
            swal.fire({
                text: description,
                icon: 'success',
                confirmButtonText: '确定',
                backdrop: "rgba(0,0,0,.3)",
                allowOutsideClick: false,
                allowEscapeKey: false,
            }).then(confirmCallback || noop);
        },
        // 失败
        fail: function (swal, description, confirmCallback) {
            swal.fire({
                text: description,
                icon: 'error',
                confirmButtonText: '确定',
                backdrop: "rgba(0,0,0,.3)",
                allowOutsideClick: false,
                allowEscapeKey: false,
            }).then(confirmCallback || noop);
        },
        // warning
        warning: function (swal, description, confirmCallback) {
            swal.fire({
                text: description,
                icon: 'warning',
                confirmButtonText: '确定',
                backdrop: "rgba(0,0,0,.3)",
                allowOutsideClick: false,
                allowEscapeKey: false,
            }).then(confirmCallback || noop);
        },
        // info
        info: function (swal, description, confirmCallback) {
            swal.fire({
                text: description,
                icon: 'info',
                confirmButtonText: '确定',
                backdrop: "rgba(0,0,0,.3)",
                allowOutsideClick: false,
                allowEscapeKey: false,
            }).then(confirmCallback || noop);
        },
        // 倒计时
        timer: function (swal, title, description, successCallback, timer) {
            var that = this;
            successCallback = successCallback || new Function();
            timer = timer || 5000;
            var timerInterval;
            swal.fire({
                title: title,
                html: "<b></b>秒后, " + description,
                timer: timer, // 5s
                timerProgressBar: true,
                backdrop: "rgba(0,0,0,.3)",
                allowOutsideClick: false,
                allowEscapeKey: false,
                didOpen: function () {
                    swal.showLoading();
                    var b = swal.getHtmlContainer().querySelector('b');
                    var millionSecond;
                    timerInterval = setInterval(function () {
                        millionSecond = swal.getTimerLeft();
                        b.textContent = Math.ceil(millionSecond / 1000) + '';
                    }, 100)
                },
                willClose: function () {
                    clearInterval(timerInterval)
                },
            }).then(function (result) {
                /* Read more about handling dismissals below */
                if (result.dismiss === swal.DismissReason.timer) {
                    successCallback.call(that);
                }
            });
        },
    };

    /**
     * 获取变量的数据类型
     * @param variant
     * @returns {*}
     */
    Ym2Constructor.prototype.typeOf = function (variant) {
        var toString = global.Object.prototype.toString;
        var map = {
            '[object Boolean]': 'boolean',
            '[object Number]': 'number',
            '[object String]': 'string',
            '[object Function]': 'function',
            '[object Array]': 'array',
            '[object Date]': 'date',
            '[object RegExp]': 'regExp',
            '[object Undefined]': 'undefined',
            '[object Null]': 'null',
            '[object Object]': 'object'
        };
        return map[toString.call(variant)];
    };

    /**
     * 获取对象或数组的键
     * @param obj
     * @returns {Array}
     */
    Ym2Constructor.prototype.keys = function (obj) {
        var keys = [];
        for (var i in obj) {
            keys.push(i);
        }

        return keys;
    };

    /**
     * 获取当前项目的语言
     * @returns {*|string}
     */
    Ym2Constructor.prototype.language = function () {
        return jQuery('html').attr('lang') || 'zh-CN';
    };

    /**
     * 获取当前时间戳
     * @returns {number}
     */
    Ym2Constructor.prototype.getTime = function () {
        var DateObj = new Date();
        return Math.floor(DateObj.getTime() / 1000);
    };

    /**
     * URL拼接参数
     * @param url
     * @param params
     * @returns {*}
     */
    Ym2Constructor.prototype.addUrlQueryParam = function (url, params) {
        params = params || {};

        if (this.keys(params).length) {
            var hash = [];
            var queryParam = jQuery.param(params);

            // 处理#
            if (url.indexOf('#') > -1) {
                var map = url.split('#');
                url = map.shift();
                hash = map;
            }

            // 处理?
            if (url.indexOf('?') > -1) {
                if (url.slice(-1) === '&') {
                    url += queryParam;
                } else {
                    url += '&' + queryParam;
                }
            } else {
                url += '?' + queryParam;
            }

            // 拼接#
            hash.forEach(function (item) {
                url += '#' + item;
            });

            return url;
        }

        return url;
    };

    /**
     * 字符串首字母大写
     * @param str
     * @return {string}
     */
    Ym2Constructor.prototype.firstUpperCase = function (str) {
        return str.toString()[0].toUpperCase() + str.toString().slice(1);
    };

    /**
     * 小驼峰转下划线
     * @param str
     * @param delimiter 分隔符
     * @return {string}
     */
    Ym2Constructor.prototype.camelcaseToHyphen = function (str, delimiter) {
        delimiter = delimiter || '_';
        return (str.toString()[0].toLowerCase() + str.toString().slice(1)).replace(/([A-Z])/g, `${delimiter}$1`).toLowerCase();
    };

    /**
     * 判断数组或对象中是否存在指定值
     * @param value
     * @param validList
     * @return {boolean}
     */
    Ym2Constructor.prototype.oneOf = function (value, validList) {
        var varType = Object.prototype.toString.call(validList);
        if (varType === '[object Object]') {
            for (var key in validList) {
                if (validList[key] === value) {
                    return true;
                }
            }

            return false;
        } else if (varType === '[object Array]') {
            for (var i = 0; i < validList.length; i++) {
                if (value === validList[i]) {
                    return true;
                }
            }

            return false;
        } else {
            throw 'Argument [validList] type error.';
        }
    };

    /**
     * clone对象或数组
     * @param _deepCopy
     * @return {*}
     */
    Ym2Constructor.prototype.deepCopy = function (_deepCopy) {
        function deepCopy() {
            return _deepCopy.apply(this, arguments);
        }

        deepCopy.toString = function () {
            return _deepCopy.toString();
        };

        return deepCopy;
    }(function (data) {
        var t = this.typeOf(data);
        var o = void 0;

        if (t === 'array') {
            o = [];
        } else if (t === 'object') {
            o = {};
        } else {
            return data;
        }

        if (t === 'array') {
            for (var i = 0; i < data.length; i++) {
                o.push(this.deepCopy(data[i]));
            }
        } else if (t === 'object') {
            for (var _i in data) {
                o[_i] = this.deepCopy(data[_i]);
            }
        }

        return o;
    });

    /**
     * 向上取整保留n位
     * @param n
     * @param precision
     * @returns {number}
     */
    Ym2Constructor.prototype.xceil = function (n, precision) {
        precision = precision || 2;
        var multiplier = Math.pow(10, precision);

        return Math.ceil(this.numberMul(n, multiplier)) / multiplier;
    };

    /**
     * 向下取整保留n位
     * @param n
     * @param precision
     * @returns {number}
     */
    Ym2Constructor.prototype.xfloor = function (n, precision) {
        precision = precision || 2;
        var multiplier = Math.pow(10, precision);

        return Math.floor(this.numberMul(n, multiplier)) / multiplier;
    };

    /**
     * 四舍五入取整保留n位
     * @param n
     * @param precision
     * @returns {number}
     */
    Ym2Constructor.prototype.xround = function (n, precision) {
        precision = precision || 2;
        var multiplier = Math.pow(10, precision);
        return Math.round(this.numberMul(n, multiplier)) / multiplier;
    };

    /**
     * 高精度加法
     * @param n1
     * @param n2
     * @return {number}
     */
    Ym2Constructor.prototype.numberAdd = function (n1, n2) {
        var r1 = void 0,
            r2 = void 0,
            m = void 0;
        try {
            r1 = n1.toString().split('.')[1].length;
        } catch (e) {
            r1 = 0;
        }

        try {
            r2 = n2.toString().split('.')[1].length;
        } catch (e) {
            r2 = 0;
        }

        m = Math.pow(10, Math.max(r1, r2));
        var a = parseInt(n1 * m);
        var b = parseInt(n2 * m);

        return (a + b) / m;
    };

    /**
     * 高精度减法
     * @param n1
     * @param n2
     * @return {*|number}
     */
    Ym2Constructor.prototype.numberSub = function (n1, n2) {
        return this.numberAdd(n1, -n2);
    };

    /**
     * 高精度乘法
     * @param n1
     * @param n2
     * @return {number}
     */
    Ym2Constructor.prototype.numberMul = function (n1, n2) {
        var m = 0,
            s1 = n1.toString(),
            s2 = n2.toString();
        try {
            m += s1.split('.')[1].length;
        } catch (e) {
        }

        try {
            // m是累加结果
            m += s2.split('.')[1].length;
        } catch (e) {
        }

        return Number(s1.replace('.', '')) * Number(s2.replace('.', '')) / Math.pow(10, m);
    };

    /**
     * 高精度除法
     * @param n1
     * @param n2
     * @return {number}
     */
    Ym2Constructor.prototype.numberDiv = function (n1, n2) {
        var r1 = void 0,
            r2 = void 0,
            m = void 0;
        try {
            r1 = n1.toString().split('.')[1].length;
        } catch (e) {
            r1 = 0;
        }

        try {
            r2 = n2.toString().split('.')[1].length;
        } catch (e) {
            r2 = 0;
        }

        m = Math.pow(10, Math.max(r1, r2));
        var a = parseInt(n1 * m);
        var b = parseInt(n2 * m);

        return a / b;
    };

    /**
     * 数字单位转换
     * @param {int|float|string} number
     * @param {boolean} asArray
     * @return {string|array}
     */
    Ym2Constructor.prototype.unit = function (number, asArray) {
        asArray = asArray || false;
        if (!number) {
            return asArray ? ['--', ''] : '--';
        }
        var value = Number(number);
        value = isNaN(value) ? 0 : value;

        var unit = '',
            z = 10000,  // 万
            y = 100000000,  // 亿
            q = 100000000000, // 千亿
            w = 1000000000000; // 万亿
        if (value >= z && value < y) {
            unit = '万';
            value = this.xfloor(this.numberDiv(value, z));
        } else if (value >= y && value < q) {
            unit = '亿';
            value = this.xfloor(this.numberDiv(value, y));
        } else if (value >= q && value < w) {
            unit = '千亿';
            value = this.xfloor(this.numberDiv(value, q));
        } else if (value >= w) {
            unit = '万亿';
            value = this.xfloor(this.numberDiv(value, w));
        }

        return asArray ? [value, unit] : (value + unit);
    };

    /**
     * 解析js脚本
     * @param script
     * @returns {*}
     */
    Ym2Constructor.prototype.evalScript = function (script) {
        var fn = new Function("return " + script);
        return fn();
    };

    /**
     * 像localStorage一样操作cookie
     * @type {{
     *      removeItem: ((function(*, *, *): (boolean))|*),
     *      keys: (function(): string[]),
     *      hasItem: (function(*): boolean),
     *      getItem: (function(*): *),
     *      setItem: ((function(*, *, *, *, *, *): (boolean))|*)
     * }}
     */
    Ym2Constructor.prototype.cookie = (function () {
        /*\
        |*|
        |*|  :: cookies.js ::
        |*|
        |*|  A complete cookies reader/writer framework with full unicode support.
        |*|
        |*|  https://developer.mozilla.org/zh-CN/docs/DOM/document.cookie
        |*|
        |*|  This framework is released under the GNU Public License, version 3 or later.
        |*|  http://www.gnu.org/licenses/gpl-3.0-standalone.html
        |*|
        |*|  Syntaxes:
        |*|
        |*|  * docCookies.setItem(name, value[, end[, path[, domain[, secure]]]])
        |*|  * docCookies.getItem(name)
        |*|  * docCookies.removeItem(name[, path], domain)
        |*|  * docCookies.hasItem(name)
        |*|  * docCookies.keys()
        |*|
        \*/
        return {
            getItem: function (sKey) {
                return (
                    decodeURIComponent(
                        document.cookie.replace(
                            new RegExp(
                                "(?:(?:^|.*;)\\s*" +
                                encodeURIComponent(sKey).replace(/[-.+*]/g, "\\$&") +
                                "\\s*\\=\\s*([^;]*).*$)|^.*$",
                            ),
                            "$1",
                        ),
                    ) || null
                );
            },
            setItem: function (sKey, sValue, vEnd, sPath, sDomain, bSecure) {
                if (!sKey || /^(?:expires|max\-age|path|domain|secure)$/i.test(sKey)) {
                    return false;
                }
                var sExpires = "";
                if (vEnd) {
                    switch (vEnd.constructor) {
                        case Number:
                            sExpires =
                                vEnd === Infinity
                                    ? "; expires=Fri, 31 Dec 9999 23:59:59 GMT"
                                    : "; max-age=" + vEnd;
                            break;
                        case String:
                            sExpires = "; expires=" + vEnd;
                            break;
                        case Date:
                            sExpires = "; expires=" + vEnd.toUTCString();
                            break;
                    }
                }
                document.cookie =
                    encodeURIComponent(sKey) +
                    "=" +
                    encodeURIComponent(sValue) +
                    sExpires +
                    (sDomain ? "; domain=" + sDomain : "") +
                    (sPath ? "; path=" + sPath : "") +
                    (bSecure ? "; secure" : "");
                return true;
            },
            removeItem: function (sKey, sPath, sDomain) {
                if (!sKey || !this.hasItem(sKey)) {
                    return false;
                }
                document.cookie =
                    encodeURIComponent(sKey) +
                    "=; expires=Thu, 01 Jan 1970 00:00:00 GMT" +
                    (sDomain ? "; domain=" + sDomain : "") +
                    (sPath ? "; path=" + sPath : "");
                return true;
            },
            hasItem: function (sKey) {
                return new RegExp(
                    "(?:^|;\\s*)" +
                    encodeURIComponent(sKey).replace(/[-.+*]/g, "\\$&") +
                    "\\s*\\=",
                ).test(document.cookie);
            },
            keys: /* optional method: you can safely remove it! */ function () {
                var aKeys = document.cookie
                    .replace(/((?:^|\s*;)[^\=]+)(?=;|$)|^\s*|\s*(?:\=[^;]*)?(?:\1|$)/g, "")
                    .split(/\s*(?:\=[^;]*)?;\s*/);
                for (var nIdx = 0; nIdx < aKeys.length; nIdx++) {
                    aKeys[nIdx] = decodeURIComponent(aKeys[nIdx]);
                }
                return aKeys;
            },
        };

    })();

    return new Ym2Constructor();
});